package ch.elca.lol.puissance4.board;

import ch.elca.lol.puissance4.game.Token;

/** BoardData using a long, works only for 7x6 boards.*/ 
 /* Performance, right now, is not significantly better than LinMat, but this implementation may perform better
 * if we implement some kind of caching/iterning of Boards, because we can compare boards in a single operation 
 * with this implementation. */
public final class LongBoard implements BoardData {
	/*  Bitmap + heightmap representing the 7x6 board.
	 *  First bit is always 0.
	 *  Next 21 bits are 7 chunks of 3 bits, representing the height of tokens on each column, rightmost column first.
	 *  Next (and last) 42 bits represent the value of each slot. 1 is a X, 0 is a O iff the height of the column is
	 *  high enough. Bits are ordered from right to left, top to bottom. */
	private final long tokens;

	private final int w = 7;
	private final int h = 6;
	
	private final long mask1 = 0x1;
	private final long mask3 = 0x7; // binary 111 
	private final int bmSize = w*h;
	
	public LongBoard() {
		this.tokens = 0;
	}
	
	private LongBoard(long tokens) {
		this.tokens = tokens;
	}
	
	public int getWidth() {
		return w;
	}
	
	public int getHeight() {
		return h;
	}
	
	public LongBoard set(int x, int y, Token t) {
		long tok = tokens;
		
		// Compute new height
		long colH = bottomOfCol(x) + 1;

		// Compute shift
		int shift = x*3 + bmSize;
		
		// Set height in heightmap
		long mask = Long.MAX_VALUE & (mask3 << shift); // All 1 except for the height bits
		tok &= ~mask; // Erase old height
		tok |= (colH << shift); // Set new height
		
		// Set token to 1 in bitmap if necessary
		if(t == Token.X) {
			tok |= mask1 << (x + y*w);
		} 
		
		return new LongBoard(tok);
	}
	
	public int bottomOfCol(int col) {
		int shift = col*3 + bmSize;
		return (int)((tokens & (mask3 << shift)) >> shift); 
	}
	
	public boolean isNull(int x, int y) {
		int bot = bottomOfCol(x);
		return y >= bot;
	}
	
	public Token get(int x, int y) {
		boolean raw = rawGet(x, y);
		if(raw)
			return Token.X;
		else
			return isNull(x,y) ? null : Token.O;
	}
	
	private boolean rawGet(int x, int y) {
		int idx = x + y*w;
		return shift1AndMask(idx);
	}

	private final boolean shift1AndMask(int shift) {
		return toBool(tokens & (mask1 << shift));
	}
	
	private final static boolean toBool(long l) {
		return l != 0;
	}
	
	protected String printLong(long l) {
		return Long.toBinaryString(l | Long.MIN_VALUE);
	}
	
	public String  toString() {
		return printLong(tokens);
	}
}
